<!--
  Copyright: (c) 2016-2020, ST-One Ltda., Guilherme Francescon Cittolin <guilherme@st-one.io>
  GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
-->

<script type="text/x-red" data-template-name="s7 endpoint">
    <div class="form-row">
		<ul style="background: #fff; min-width: 600px; margin-bottom: 20px;" id="node-config-s7-endpoint-tabs"></ul>
	</div>
	<div id="node-config-s7-endpoint-tabs-content" style="min-height: 170px;">
		<div id="s7-endpoint-tab-connection" style="display:none">
			<div class="form-row">
				<label for="node-config-input-transport"><i class="fa fa-plug"></i> <span data-i18n="s7.endpoint.label.transport"></span></label>
				<select type="text" id="node-config-input-transport">
					<option value="iso-on-tcp" data-i18n="s7.endpoint.transport.iso-on-tcp"></option>
					<option value="mpi-s7" data-i18n="s7.endpoint.transport.mpi-s7"></option>
				</select>
			</div>
			<div id="s7-endpoint-transport-iso-on-tcp" style="display:none; padding-left: 20px;">
				<div class="form-row">
					<label for="node-config-input-address"><i class="fa fa-globe"></i> <span data-i18n="s7.endpoint.label.address"></span></label>
					<input class="input-append-left" type="text" id="node-config-input-address" data-i18n="[placeholder]s7.endpoint.placeholder.address" style="width: 40%;">
					<label for="node-config-input-port" style="margin-left: 10px; width: auto; "> <span data-i18n="s7.endpoint.label.port"></span></label>
					<input type="text" id="node-config-input-port" data-i18n="[placeholder]s7.endpoint.label.port" style="width: 50px; margin-right: 10px;">
					<a id="node-config-lookup-plc-pn" class="btn red-ui-button disabled" data-i18n="[title]s7.endpoint.label.search"><i class="fa fa-search"></i></a>
					<a id="node-config-flash-plc-pn" class="btn red-ui-button disabled" data-i18n="[title]s7.endpoint.label.flash-led"><i class="fa fa-lightbulb-o"></i></a>
				</div>
				<div class="form-row">
					<label for="node-config-input-connmode"><i class="fa fa-sliders"></i> <span data-i18n="s7.endpoint.label.connmode"></span></label>
					<select type="text" id="node-config-input-connmode">
						<option value="rack-slot" data-i18n="s7.endpoint.connmode.rack-slot"></option>
						<option value="tsap" data-i18n="s7.endpoint.connmode.tsap"></option>
					</select>
				</div>
				<div class="form-row" id="node-config-s7-endpoint-mode-rackslot">
					<label for="node-config-input-rack"><i class="fa fa-sitemap"></i> <span data-i18n="s7.endpoint.label.rack"></span></label>
					<input class="input-append-left" type="text" id="node-config-input-rack" data-i18n="[placeholder]s7.endpoint.label.rack" style="width: 50px">
					<label for="node-config-input-slot" style="margin-left: 10px; width: 35px; "> <span data-i18n="s7.endpoint.label.slot"></span></label>
					<input type="text" id="node-config-input-slot" data-i18n="[placeholder]s7.endpoint.label.slot" style="width:50px">
				</div>
				<div class="form-row" id="node-config-s7-endpoint-mode-tsap">
					<label for="node-config-input-localtsaphi"><i class="fa fa-exchange"></i> <span data-i18n="s7.endpoint.label.tsap-local"></span></label>
					<input class="input-append-left" type="text" id="node-config-input-localtsaphi" style="width: 35px"><span><b>.</b></span>
					<input class="input-append-left" type="text" id="node-config-input-localtsaplo" style="width: 35px">
					<label for="node-config-input-remotetsaphi" style="margin-left: 10px"> <span data-i18n="s7.endpoint.label.tsap-remote"></span></label>
					<input class="input-append-left" type="text" id="node-config-input-remotetsaphi" style="width: 35px"><span><b>.</b></span>
					<input class="input-append-left" type="text" id="node-config-input-remotetsaplo" style="width: 35px">
				</div>
			</div>
			<div id="s7-endpoint-transport-mpi-s7" style="display:none; padding-left: 20px;">
				<div class="form-row">
					<label for="node-config-input-adapter"><i class="fa fa-exchange"></i> <span data-i18n="s7.endpoint.label.adapter"></span></label>
					<input type="text" id="node-config-input-adapter" data-i18n="[placeholder]s7.endpoint.label.adapter">
					<input type="text" id="s7-endpoint-adapter-placeholder" data-i18n="[placeholder]s7.endpoint.label.adapterplaceholder" disabled style="display:none">
				</div>
				<div class="form-row">
					<label for="node-config-input-busaddr"><i class="fa fa-sitemap"></i> <span data-i18n="s7.endpoint.label.busaddr"></span></label>
					<input type="text" id="node-config-input-busaddr" style="width: 60px;">
				</div>
			</div>
			<div class="form-row">
				<label for="node-config-input-cycletime"><i class="fa fa-refresh"></i> <span data-i18n="s7.endpoint.label.cycletime"></span></label>
				<input type="text" id="node-config-input-cycletime" data-i18n="[placeholder]s7.endpoint.label.cycletime" style="width: 60px;"> <span>ms</span>
			</div>
			<div class="form-row">
				<label for="node-config-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="s7.endpoint.label.timeout"></span></label>
				<input type="text" id="node-config-input-timeout" data-i18n="[placeholder]s7.endpoint.label.timeout" style="width: 60px;"> <span>ms</span>
			</div>
			<div class="form-row">
				<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
				<input type="text" id="node-config-input-name" data-i18n="[placeholder]s7.label.name">
			</div>
		</div>
		<div id="s7-endpoint-tab-variables" style="display:none">
			<div class="form-row" style="margin-bottom:0;">
				<label><i class="fa fa-list"></i> <span data-i18n="s7.endpoint.label.variables.list"></span></label>
			</div>
			<div class="form-row node-input-variables-container-row" style="margin-bottom: 0px;">
				<div id="node-config-input-variables-container-div" style="box-sizing: border-box; border-radius: 5px; height: 300px; padding: 5px; border: 1px solid #ccc; overflow-y:scroll;">
					<ol id="node-config-input-variables-container" style=" list-style-type:none; margin: 0;"></ol>
				</div>
			</div>
			<div class="form-row">
				<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-export" style="margin: 4px; float: right"><i class="fa fa-download"></i> <span data-i18n="s7.endpoint.label.variables.export"></span></a>
				<input type="file" id="node-config-s7-endpoint-var-import" style="display: none"/>
				<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-import-btn" style="margin: 4px; float: right"><i class="fa fa-upload"></i> <span data-i18n="s7.endpoint.label.variables.import"></span></a>
				<a href="#" class="editor-button editor-button-small" id="node-config-input-add-variable" style="margin: 4px;"><i class="fa fa-plus"></i> <span data-i18n="s7.endpoint.label.variables.add"></span></a>
				<a href="#" class="editor-button editor-button-small" id="node-config-s7-endpoint-var-clean" style="margin: 4px;"><i class="fa fa-trash-o"></i> <span data-i18n="s7.endpoint.label.variables.clean"></span></a>
			</div>
		</div>
	</div>
</script>

<script type="text/x-red" data-help-name="s7 endpoint">
	<p>Configures the connection to a PLC</p>
	<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>

	<h3>Details</h3>
	<p>
		The <b>Cycle time</b> configuration specifies the time interval in which
		all variables will be read from the PLC. A value of <code>0</code> disables
		automatic reading, and then a manual triggering is required from the 
		<i>s7 control</i> node
	</p>
	<p>
		The <b>Timeout</b> parameter sets the maximum amount of time that the PLC
		may take to answer our requests. It may be desirable to raise this time if
		the network is busy of it's latency is high
	</p>
	<p>
		It's possible to log the PLC communication by setting the <b>Debug</b> option
		to "On" of "Off". By default, the behavior is controlled by the <code>-v</code>
		command-line parameter when running Node-RED
	</p>
	<p>
		In the <b>Transport</b> option it is possible to select method used
		to reach the PLC. Different connection options are available depending
		on the selected option:
	</p>
	<h4><i>Ethernet (ISO-on-TCP)</i></h4>
	<p>
		Connects to the specified ethernet address and port number of the PLC.
	</p>
	<ul>
		<li>
			<b>Mode</b>: selects between <i>Rack/Slot</i> and <i>TSAP</i>
			connection modes. Usually the <i>Rack/Slot</i> mode is used, except when
			connecting to Logo devices where the only option is the <i>TSAP</i>
			method
		</li>
	</ul>
	<h4><i>MPI/PPI/DP Adapter</i></h4>
	<p>
		Connects to the PLC using MPI/PPI/DP Adapters available on the system. Requires
		the installation of <code>node-red-contrib-mpi-s7</code>.
	</p>
	<ul>
		<li>
			<b>Address</b>: specifies the address on the bus of the PLC 
			that we will communicate to.
		</li>
	</ul>
	

	<h3>Notes on S7-1200/S7-1500</h3>
	<p>
		Some extra configuration may be needed on the S7-1200 and S7-1500 series
		of PLCS in order to access their data:
		<ul>
			<li>
				"Optimized block access" must be disabled for the DBs we want 
				to access
			</li>
			<li>
				In the "Protection" section of the CPU Properties, enable the 
				"Permit access with PUT/GET" checkbox
			</li>
		</ul>
	</p>

	<h3>Variable addressing</h3>
	<p>
		The variables and their addresses configured on the <strong>S7 Endpoint</strong> 
		follow a slightly different scheme than used on Step 7 or TIA Portal. 
		Here are some examples that may guide you on addressing your variables:
	</p>
	<style>
		table {
		padding: 0; }
		table tr {
			border-top: 1px solid #cccccc;
			background-color: white;
			margin: 0;
			padding: 0; }
			table tr:nth-child(2n) {
			background-color: #f8f8f8; }
			table tr th {
			font-weight: bold;
			border: 1px solid #cccccc;
			text-align: left;
			margin: 0;
			padding: 6px 13px; }
			table tr td {
			border: 1px solid #cccccc;
			text-align: left;
			margin: 0;
			padding: 6px 13px; }
			table tr th :first-child, table tr td :first-child {
			margin-top: 0; }
			table tr th :last-child, table tr td :last-child {
			margin-bottom: 0; }
	</style>
	<table>
		<thead>
			<tr>
				<th>Address</th>
				<th>Step7 equivalent</th>
				<th>JS Data type</th>
				<th>Description</th>
			</tr>
		</thead>
		<tbody>
			<tr>
				<td><code>DB5,X0.1</code></td>
				<td><code>DB5.DBX0.1</code></td>
				<td>Boolean</td>
				<td>Bit 1 of byte 0 of DB 5</td>
			</tr>
			<tr>
				<td><code>DB23,B1</code> or <code>DB23,BYTE1</code></td>
				<td><code>DB23.DBB1</code></td>
				<td>Number</td>
				<td>Byte 1 (0-255) of DB 23</td>
			</tr>
			<tr>
				<td><code>DB100,C2</code> or <code>DB100,CHAR2</code></td>
				<td><code>DB100.DBB2</code></td>
				<td>String</td>
				<td>Byte 2 of DB 100 as a Char</td>
			</tr>
			<tr>
				<td><code>DB42,I3</code> or <code>DB42,INT3</code></td>
				<td><code>DB42.DBW3</code></td>
				<td>Number</td>
				<td>Signed 16-bit number at byte 3 of DB 42</td>
			</tr>
			<tr>
				<td><code>DB57,WORD4</code></td>
				<td><code>DB57.DBW4</code></td>
				<td>Number</td>
				<td>Unsigned 16-bit number at byte 4 of DB 57</td>
			</tr>
			<tr>
				<td><code>DB13,DI5</code> or <code>DB13,DINT5</code></td>
				<td><code>DB13.DBD5</code></td>
				<td>Number</td>
				<td>Signed 32-bit number at byte 5 of DB 13</td>
			</tr>
			<tr>
				<td><code>DB19,DW6</code> or <code>DB19,DWORD6</code></td>
				<td><code>DB19.DBD6</code></td>
				<td>Number</td>
				<td>Unsigned 32-bit number at byte 6 of DB 19</td>
			</tr>
			<tr>
				<td><code>DB21,R7</code> or <code>DB21,REAL7</code></td>
				<td><code>DB19.DBD7</code></td>
				<td>Number</td>
				<td>Floating point 32-bit number at byte 7 of DB 21</td>
			</tr>
			<tr>
				<td><code>DB2,S7.10</code>*</td>
				<td>-</td>
				<td>String</td>
				<td>String of length 10 starting at byte 7 of DB 2</td>
			</tr>
			<tr>
				<td><code>I1.0</code> or <code>E1.0</code></td>
				<td><code>I1.0</code> or <code>E1.0</code></td>
				<td>Boolean</td>
				<td>Bit 0 of byte 1 of input area</td>
			</tr>
			<tr>
				<td><code>Q2.1</code> or <code>A2.1</code></td>
				<td><code>Q2.1</code> or <code>A2.1</code></td>
				<td>Boolean</td>
				<td>Bit 1 of byte 2 of output area</td>
			</tr>
			<tr>
				<td><code>M3.2</code></td>
				<td><code>QM3.2</code></td>
				<td>Boolean</td>
				<td>Bit 2 of byte 3 of memory area</td>
			</tr>
			<tr>
				<td><code>IB4</code> or <code>EB4</code></td>
				<td><code>IB4</code> or <code>EB4</code></td>
				<td>Number</td>
				<td>Byte 4 (0 -255) of input area</td>
			</tr>
			<tr>
				<td><code>QB5</code> or <code>AB5</code></td>
				<td><code>QB5</code> or <code>AB5</code></td>
				<td>Number</td>
				<td>Byte 5 (0 -255) of output area</td>
			</tr>
			<tr>
				<td><code>MB6</code></td>
				<td><code>MB6</code></td>
				<td>Number</td>
				<td>Byte 6 (0 -255) of memory area</td>
			</tr>
			<tr>
				<td><code>IC7</code> or <code>EC7</code></td>
				<td><code>IB7</code> or <code>EB7</code></td>
				<td>String</td>
				<td>Byte 7 of input area as a Char</td>
			</tr>
			<tr>
				<td><code>QC8</code> or <code>AC8</code></td>
				<td><code>QB8</code> or <code>AB8</code></td>
				<td>String</td>
				<td>Byte 8 of output area as a Char</td>
			</tr>
			<tr>
				<td><code>MC9</code></td>
				<td><code>MB9</code></td>
				<td>String</td>
				<td>Byte 9 of memory area as a Char</td>
			</tr>
			<tr>
				<td><code>II10</code> or <code>EI10</code></td>
				<td><code>IW10</code> or <code>EW10</code></td>
				<td>Number</td>
				<td>Signed 16-bit number at byte 10 of input area</td>
			</tr>
			<tr>
				<td><code>QI12</code> or <code>AI12</code></td>
				<td><code>QW12</code> or <code>AW12</code></td>
				<td>Number</td>
				<td>Signed 16-bit number at byte 12 of output area</td>
			</tr>
			<tr>
				<td><code>MI14</code></td>
				<td><code>MW14</code></td>
				<td>Number</td>
				<td>Signed 16-bit number at byte 14 of memory area</td>
			</tr>
			<tr>
				<td><code>IW16</code> or <code>EW16</code></td>
				<td><code>IW16</code> or <code>EW16</code></td>
				<td>Number</td>
				<td>Unsigned 16-bit number at byte 16 of input area</td>
			</tr>
			<tr>
				<td><code>QW18</code> or <code>AW18</code></td>
				<td><code>QW18</code> or <code>AW18</code></td>
				<td>Number</td>
				<td>Unsigned 16-bit number at byte 18 of output area</td>
			</tr>
			<tr>
				<td><code>MW20</code></td>
				<td><code>MW20</code></td>
				<td>Number</td>
				<td>Unsigned 16-bit number at byte 20 of memory area</td>
			</tr>
			<tr>
				<td><code>IDI22</code> or <code>EDI22</code></td>
				<td><code>ID22</code> or <code>ED22</code></td>
				<td>Number</td>
				<td>Signed 32-bit number at byte 22 of input area</td>
			</tr>
			<tr>
				<td><code>QDI24</code> or <code>ADI24</code></td>
				<td><code>QD24</code> or <code>AD24</code></td>
				<td>Number</td>
				<td>Signed 32-bit number at byte 24 of output area</td>
			</tr>
			<tr>
				<td><code>MDI26</code></td>
				<td><code>MD26</code></td>
				<td>Number</td>
				<td>Signed 32-bit number at byte 26 of memory area</td>
			</tr>
			<tr>
				<td><code>ID28</code> or <code>ED28</code></td>
				<td><code>ID28</code> or <code>ED28</code></td>
				<td>Number</td>
				<td>Unsigned 32-bit number at byte 28 of input area</td>
			</tr>
			<tr>
				<td><code>QD30</code> or <code>AD30</code></td>
				<td><code>QD30</code> or <code>AD30</code></td>
				<td>Number</td>
				<td>Unsigned 32-bit number at byte 30 of output area</td>
			</tr>
			<tr>
				<td><code>MD32</code></td>
				<td><code>MD32</code></td>
				<td>Number</td>
				<td>Unsigned 32-bit number at byte 32 of memory area</td>
			</tr>
			<tr>
				<td><code>IR34</code> or <code>ER34</code></td>
				<td><code>IR34</code> or <code>ER34</code></td>
				<td>Number</td>
				<td>Floating point 32-bit number at byte 34 of input area</td>
			</tr>
			<tr>
				<td><code>QR36</code> or <code>AR36</code></td>
				<td><code>QR36</code> or <code>AR36</code></td>
				<td>Number</td>
				<td>Floating point 32-bit number at byte 36 of output area</td>
			</tr>
			<tr>
				<td><code>MR38</code></td>
				<td><code>MR38</code></td>
				<td>Number</td>
				<td>Floating point 32-bit number at byte 38 of memory area</td>
			</tr>
			<tr>
				<td><code>DB1,DT0</code></td>
				<td><code>-</code></td>
				<td>Date**</td>
				<td>A timestamp in the DATE_AND_TIME format</td>
			</tr>
			<tr>
				<td><code>DB1,DTZ10</code></td>
				<td><code>-</code></td>
				<td>Date**</td>
				<td>A timestamp in the DATE_AND_TIME format, in UTC</td>
			</tr>
			<tr>
				<td><code>DB2,DTL2</code></td>
				<td><code>-</code></td>
				<td>Date**</td>
				<td>A timestamp in the DTL format</td>
			</tr>
			<tr>
				<td><code>DB1,DTLZ12</code></td>
				<td><code>-</code></td>
				<td>Date**</td>
				<td>A timestamp in the DTL format, in UTC</td>
			</tr>
		</tbody>
	</table>

	<p>*) Note that strings on the PLC uses 2 extra bytes at start for size/length of the string</p>
	<p>**) Note that javascript's <code>Date</code> are <i>always</i> represented in UTC. Please use other nodes like <a href="https://flows.nodered.org/node/node-red-contrib-moment" target="_blank">node-red-contrib-moment</a> to properly handle type conversions</p>
</script>

<script type="text/javascript">
	/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
	var saveAs = saveAs || function (e) { "use strict"; if (typeof e === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) { return } var t = e.document, n = function () { return e.URL || e.webkitURL || e }, r = t.createElementNS("http://www.w3.org/1999/xhtml", "a"), o = "download" in r, a = function (e) { var t = new MouseEvent("click"); e.dispatchEvent(t) }, i = /constructor/i.test(e.HTMLElement) || e.safari, f = /CriOS\/[\d]+/.test(navigator.userAgent), u = function (t) { (e.setImmediate || e.setTimeout)(function () { throw t }, 0) }, s = "application/octet-stream", d = 1e3 * 40, c = function (e) { var t = function () { if (typeof e === "string") { n().revokeObjectURL(e) } else { e.remove() } }; setTimeout(t, d) }, l = function (e, t, n) { t = [].concat(t); var r = t.length; while (r--) { var o = e["on" + t[r]]; if (typeof o === "function") { try { o.call(e, n || e) } catch (a) { u(a) } } } }, p = function (e) { if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)) { return new Blob([String.fromCharCode(65279), e], { type: e.type }) } return e }, v = function (t, u, d) { if (!d) { t = p(t) } var v = this, w = t.type, m = w === s, y, h = function () { l(v, "writestart progress write writeend".split(" ")) }, S = function () { if ((f || m && i) && e.FileReader) { var r = new FileReader; r.onloadend = function () { var t = f ? r.result : r.result.replace(/^data:[^;]*;/, "data:attachment/file;"); var n = e.open(t, "_blank"); if (!n) e.location.href = t; t = undefined; v.readyState = v.DONE; h() }; r.readAsDataURL(t); v.readyState = v.INIT; return } if (!y) { y = n().createObjectURL(t) } if (m) { e.location.href = y } else { var o = e.open(y, "_blank"); if (!o) { e.location.href = y } } v.readyState = v.DONE; h(); c(y) }; v.readyState = v.INIT; if (o) { y = n().createObjectURL(t); setTimeout(function () { r.href = y; r.download = u; a(r); h(); c(y); v.readyState = v.DONE }); return } S() }, w = v.prototype, m = function (e, t, n) { return new v(e, t || e.name || "download", n) }; if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { return function (e, t, n) { t = t || e.name || "download"; if (!n) { e = p(e) } return navigator.msSaveOrOpenBlob(e, t) } } w.abort = function () { }; w.readyState = w.INIT = 0; w.WRITING = 1; w.DONE = 2; w.error = w.onwritestart = w.onprogress = w.onwrite = w.onabort = w.onerror = w.onwriteend = null; return m }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content); if (typeof module !== "undefined" && module.exports) { module.exports.saveAs = saveAs } else if (typeof define !== "undefined" && define !== null && define.amd !== null) { define("FileSaver.js", function () { return saveAs }) }
</script>

<script type="text/javascript">

	function validateS7Address(address) {

		if (!address) return 'ERR_PARSE_EMPTY';

		var NODES7_REGEX_ADDR = /^(?:DB(\d+),)?([A-Z]+)(\d+)(?:\.(\d+))?(?:\.(\d+))?$/;
		var NODES7_ADDRTYPE = ["E", "I", "A", "Q", "P", "M", "T", "C"];
		var NODES7_DATATYPE = ["X", "B", "C", "I", "DI", "W", "D", "DW", "R"];
		var NODES7_DATATYPE_DB = ["X", "BYTE", "CHAR", "STRING", "INT", "DINT", "WORD", "DWORD", "REAL", "DT", "DTZ", "DTL", "DTLZ", "B", "C", "S", "I", "DI", "W", "D", "DW", "R"];

		var match = address.match(NODES7_REGEX_ADDR);
		if (!match) return 'ERR_PARSE_UNKNOWN_FORMAT';

		var match_db = match[1];
		var match_area = match[2];
		var match_addr = match[3];
		var match_bitAddr = parseInt(match[4]);
		var match_arrLen = parseInt(match[5]);

		var addrType, dataType, addressOffset, dbNumber;

		addressOffset = parseInt(match_addr)
		if (isNaN(addressOffset)) return 'ERR_PARSE_ADDR_OFFSET';

		if (match_db) {
			dbNumber = parseInt(match_db);
			if (dbNumber < 1 || isNaN(dbNumber)) return 'ERR_PARSE_DB_NUMBER';
		}

		if (dbNumber) {
			if (!NODES7_DATATYPE_DB.includes(match_area)) return 'ERR_PARSE_DB_DATATYPE';
			dataType = match_area;
		} else {
			// the first char indicates the PLC area (I, Q, M, P, ...)
			addrType = match_area.charAt(0);
			// the data type (if area is P, skip the first char (I,E,Q,A))
			dataType = match_area.substr(addrType === "P" ? 2 : 1);

			// validate address type
			if (!NODES7_ADDRTYPE.includes(addrType)) return 'ERR_PARSE_AREA';

			if (dataType === "" && addrType !== "T" && addrType !== "C") {
				dataType = "X";
			}

			// validate data type
			if (!NODES7_DATATYPE.includes(dataType)) return 'ERR_PARSE_DATATYPE';
		}

		if (dataType === "X") {
			// handle array lengths and bit address
			if (isNaN(match_bitAddr)) return 'ERR_PARSE_BIT_OFFSET';
			if (match_bitAddr > 7) return 'ERR_PARSE_BIT_OFFSET';

		} else if (dataType === "STRING" || dataType === "S") {
			// match_bitAddr is the string length for string types
			if (isNaN(match_bitAddr) || match_bitAddr < 1) return 'ERR_PARSE_STRING_LEN';

		} else {
			// the array length should be at the bitAddr field, this is a syntax error
			if (!isNaN(match_arrLen)) return 'ERR_PARSE_INVALID_BIT_OFFSET';

			// the bitAddr field is actually the array length
			if (!isNaN(match_bitAddr) && match_bitAddr < 1) return 'ERR_PARSE_INVALID_ARR_LEN';
		}

		return null;
	}

	function validateAddressList(list) {
		for (var i = 0; i < list.length; i++){
			var elm = list[i];
			if (!elm.name) return false;
			if (validateS7Address(elm.addr)) return false;
		}
		return true;
	}

	function validateTSAP(num) {
		num = num.toString();
		if (num.length != 2) return false;
		if (!(/^[0-9a-fA-F]+$/.test(num))) return false;
		var i = parseInt(num, 16);
		if (isNaN(i) || i < 0 || i > 0xff) return false;
		return true;
	}

	RED.nodes.registerType('s7 endpoint', {
		category: 'config',
		defaults: {
			transport: {
				value: "iso-on-tcp"
			},
			address: {
				value: "",
				validate: function(v) {return this.transport == 'iso-on-tcp' ? !!v : true}
			},
			port: {
				value: "102",
				validate: RED.validators.number()
			},
			rack: {
				value: "0",
				validate: RED.validators.number()
			},
			slot: {
				value: "2",
				validate: RED.validators.number()
			},
			localtsaphi: {
				value: "01",
				validate: validateTSAP
			},
			localtsaplo: {
				value: "00",
				validate: validateTSAP
			},
			remotetsaphi: {
				value: "01",
				validate: validateTSAP
			},
			remotetsaplo: {
				value: "00",
				validate: validateTSAP
			},
			connmode: {
				value: "rack-slot"
			},
			adapter: {
				value: "",
				type: "mpi-s7 adapter",
				validate: function(v) {return this.transport != "mpi-s7" || !!v }
			},
			busaddr: {
				value: 2
			},
			cycletime: {
				value: 1000
			},
			timeout: {
				value: 2000
			},
			name: {
				value: ""
			},
			vartable: {
				value: [{
					name: "",
					addr: ""
				}],
				validate: validateAddressList
			}
		},
		label: function () {
			if (this.name) {
				return this.name
			} else if (this.transport == "iso-on-tcp") {
				return this.address + ":" + this.port + "@" + (
					this.connmode == 'tsap' ?
						(this.localtsaphi + "." + this.localtsaplo + "/" + this.remotetsaphi + "." + this.remotetsaplo) :
						(this.rack + ":" + this.slot))
			} else if (this.transport == "mpi-s7") {
				var adapterNode = RED.nodes.node(this.adapter);
				if (adapterNode) {
					return adapterNode.label() + "/" + this.busaddr;
				}
				return "MPI/PPI/DP @ ??/" + this.busaddr;
			} else {
				return "s7 endpoint";
			}
		},
		oneditprepare: function () {
			var self = this;
			var tt = this._.bind(this);
			var labelName = this._("s7.endpoint.label.variables.name");
			var labelAddr = this._("s7.endpoint.label.variables.addr");
			var labelDel = this._("s7.endpoint.label.variables.del");

			var connTypeList = $('#node-config-input-connmode');
			var rackSlotRow = $('#node-config-s7-endpoint-mode-rackslot');
			var tsapRow = $('#node-config-s7-endpoint-mode-tsap');
			
			var transportList = $('#node-config-input-transport');
			var transportIsoOnTCPGroup = $('#s7-endpoint-transport-iso-on-tcp');
			var transportMpiS7Group = $('#s7-endpoint-transport-mpi-s7');

			var fieldAddressIsoOnTcp = $('#node-config-input-address');
			var fieldName = $('#node-config-input-name');

			var lookupPlcPNBtn = $('#node-config-lookup-plc-pn');
			var flashPlcPNBtn = $('#node-config-flash-plc-pn');

			// put a placeholder for mpi-s7 adapter if not installed
			if (!RED.nodes.getType('mpi-s7 adapter')){
				$('#node-config-input-adapter').hide();
				$('#s7-endpoint-adapter-placeholder').show();
			}

			// Prepare tabs
			var tabs = RED.tabs.create({
				id: "node-config-s7-endpoint-tabs",
				onchange: function (tab) {
					$("#node-config-s7-endpoint-tabs-content").children().hide();
					$("#" + tab.id).show();
				}
			});
			tabs.addTab({
				id: "s7-endpoint-tab-connection",
				label: this._("s7.endpoint.label.tabs.connection")
			});
			tabs.addTab({
				id: "s7-endpoint-tab-variables",
				label: this._("s7.endpoint.label.tabs.variables")
			});
			setTimeout(function () {
				tabs.resize()
			}, 0);

			function generateVariable(variable) {
				var curTooltip;
				var previousValue = variable.addr;
				var container = $('<li/>', {
					style: "background: #fff; margin:0; padding:8px 0px; border-bottom: 1px solid #ccc;"
				});
				var row1 = $('<div/>').appendTo(container);

				var variableAddr = $('<input/>', {
					style: "width: 110px; margin-right: 10px;",
					class: "node-config-input-variable-addr",
					type: "text",
					placeholder: labelAddr
				}).appendTo(row1);

				var variableName = $('<input/>', {
					style: "width: 250px",
					class: "node-config-input-variable-name",
					type: "text",
					placeholder: labelName
				}).appendTo(row1);

				var finalspan = $('<span/>', {
					style: "float: right; margin-right: 10px;"
				}).appendTo(row1);
				var deleteButton = $('<a/>', {
					href: "#",
					class: "editor-button editor-button-small",
					style: "margin-top: 7px; margin-left: 5px;",
					title: labelDel
				}).appendTo(finalspan);

				$('<i/>', {
					class: "fa fa-remove"
				}).appendTo(deleteButton);

				deleteButton.click(function () {
					container.css({
						"background": "#fee"
					});
					container.fadeOut(150, function () {
						$(this).remove();
					});
					if (curTooltip) curTooltip.close();
				});

				variableAddr.change(function () {
					//validate address
					var curVal = variableAddr.val();
					var valError = validateS7Address(curVal);
					if (valError) {
						variableAddr.addClass('input-error')
						var errorText = tt("s7.validation." + valError);
						if (curTooltip) {
							curTooltip.setContent(errorText);
							curTooltip.open();
						} else if (RED.popover && RED.popover.tooltip){
							curTooltip = RED.popover.tooltip(variableAddr, errorText);
							curTooltip.open();
						}
					} else {
						variableAddr.removeClass('input-error');
						if(curTooltip) {
							curTooltip.close();
							curTooltip.setContent('');
							//hack to remove the popup, as Node-RED don't offer
							// and "unbind" function. May break in the future
							variableAddr.off('mouseenter mouseleave disabled');
							curTooltip = null;
						}
					}

					//update name if matching old one
					if (previousValue && variableName.val() == previousValue) {
						variableName.val(curVal);
					}
					previousValue = curVal;
				});

				//populate data
				variableAddr.val(variable.addr);
				variableName.val(variable.name);
				variableAddr.change();

				$("#node-config-input-variables-container").append(container);
			}

			function cleanVarTable() {
				$("#node-config-input-variables-container").children().remove();
			}

			function populateVarTable() {
				if (self.vartable) {
					if (typeof self.vartable == 'string') {
						self.vartable = JSON.parse(self.vartable);
					}
					for (var i = 0; i < self.vartable.length; i++) {
						generateVariable(self.vartable[i]);
					}
				}
			}

			$("#node-config-input-add-variable").click(function () {
				generateVariable({
					name: "",
					addr: ""
				});
			});

			$("#node-config-input-cycletime").spinner({
				min: 0
			});

			$("#node-config-input-timeout").spinner({
				min: 500
			});

			$("#node-config-input-busaddr").spinner({
				min: 0,
				max: 127
			});

			$("#node-config-s7-endpoint-var-clean").click(cleanVarTable);

			populateVarTable();

			// export
			function exportCSV() {
				var vars = $("#node-config-input-variables-container").children();
				var lines = [];

				vars.each(function (i) {
					var elm = $(this);
					lines.push([
						elm.find(".node-config-input-variable-addr").val(), //addr
						elm.find(".node-config-input-variable-name").val() //name
					].join(';'));
				});

				saveAs(new Blob([lines.join('\r\n')]), 's7endpoint' + (self.name ? '_' + self.name : '') + '.csv');
			}
			$('#node-config-s7-endpoint-var-export').click(exportCSV);

			// import
			function importCSV(e) {
				var file = e.target.files[0];
				if (!file) {
					return;
				}
				var reader = new FileReader();
				reader.onload = function (e) {
					var res = [], i, fields;
					var contents = e.target.result || '';
					var lines = contents.split(/[\r\n]+/);

					if (!lines.length) {
						alert('file is empty!');
						return;
					}

					for (i = 0; i < lines.length; i++) {

						lines[i] = lines[i].trim();
						if (lines[i] == '') continue;

						fields = lines[i].split(/[\t;]/);

						if (fields.length < 2) {
							alert('line must have at least two parameters, address and name');
							return;
						}
						res.push({
							addr: fields[0],
							name: fields[1]
						});
					}

					if (res.length) {
						cleanVarTable();
						self.vartable = res;
						populateVarTable();
					}
				};
				reader.readAsText(file);
			}
			$('#node-config-s7-endpoint-var-import').on('change', importCSV);
			$('#node-config-s7-endpoint-var-import-btn').click(function () {
				$('#node-config-s7-endpoint-var-import').click();
			})

			//set defaults for old flows

			if (self.connmode === undefined) {
				console.log('defaults conmode to "rack-slot"');
				self.connmode = 'rack-slot';
				self.localtsaphi = "01";
				self.localtsaplo = "00";
				self.remotetsaphi = "01";
				self.remotetsaplo = "00";
				$("#node-config-input-localtsaphi").val(self.localtsaphi);
				$("#node-config-input-localtsaplo").val(self.localtsaplo);
				$("#node-config-input-remotetsaphi").val(self.remotetsaphi);
				$("#node-config-input-remotetsaplo").val(self.remotetsaplo);
				connTypeList.val(self.connmode);
			}

			if (self.timeout === undefined) {
				self.timeout = 1500;
				$("#node-config-input-timeout").val(self.timeout);
			}

			if (self.verbose === undefined) {
				self.verbose = 'default';
				$("#node-config-input-verbose").val(self.verbose);
			}

			if (self.transport === undefined) {
				self.transport = 'iso-on-tcp';
				transportList.val(self.transport);
			}

			connTypeList.change(function () {
				if (connTypeList.val() == "rack-slot") {
					rackSlotRow.show();
					tsapRow.hide();
				} else if (connTypeList.val() == "tsap") {
					rackSlotRow.hide();
					tsapRow.show();
				} else {
					rackSlotRow.hide();
					tsapRow.hide();
				}
			});
			connTypeList.change();

			transportList.change(function() {
				var curVal = transportList.val();
				if(curVal == "iso-on-tcp") {
					transportIsoOnTCPGroup.show();
					transportMpiS7Group.hide();
				} else if (curVal == "mpi-s7") {
					transportIsoOnTCPGroup.hide();
					transportMpiS7Group.show();
				} else {
					transportIsoOnTCPGroup.hide();
					transportMpiS7Group.hide();
				}
			});
			transportList.change();

			$.getJSON('__node-red-contrib-s7/discover/available/iso-on-tcp', function(data) {
				if (data) {
					lookupPlcPNBtn.removeClass('disabled');
				}
			});

			var lookupPlcPNValue = null;
			fieldAddressIsoOnTcp.change(function() {
				lookupPlcPNValue = null;
				flashPlcPNBtn.addClass('disabled');
			})

			lookupPlcPNBtn.click(function () {
				if (lookupPlcPNBtn.hasClass('disabled')) return;

				lookupPlcPNBtn.addClass('disabled');
				var iconLookup = lookupPlcPNBtn.children('i');
				iconLookup.removeClass('fa-search').addClass('fa-spinner fa-spin fa-fw');

				$.getJSON('__node-red-contrib-s7/discover/iso-on-tcp', function (data) {
					console.log("node-red-contrib-s7 discovered PLCs", data)

					var listData = $.map(data, function(val){
						return { label: val, value: val['IP Address']}
					})

					fieldAddressIsoOnTcp.autocomplete({
						source: listData,
						minLength: 0,
						create: function (event, ui) {
							$(this).data('ui-autocomplete')._renderItem = function (ul, elm) {
								var item = elm.label;
								elmHtml = '<a>'
									+ '<small style="float: right;">' + item['Vendor Value'] + '</small>'
									+ '<b>' + item['Station Name'] + '</b>'
									+ '<br/>'
									+ '<small style="float: right;">' + item['MAC Address'] + '</small>'
									+ item['IP Address']
									+ '</a>'
								return $('<li style="padding: 2px">').attr("data-value", elm.value).append(elmHtml).appendTo(ul);
							};
						},
						select: function (event, ui) {
							var elm = ui.item.label
							
							event.preventDefault();
							event.stopPropagation();
							fieldAddressIsoOnTcp.val(elm['IP Address']);
							fieldAddressIsoOnTcp.change();


							if (!fieldName.val()){
								fieldName.val(elm['Station Name']);
							}

							lookupPlcPNValue = elm;
							flashPlcPNBtn.removeClass('disabled');
						}
					});
					
					fieldAddressIsoOnTcp.focus(function() {
						fieldAddressIsoOnTcp.autocomplete("search", "")
					});
					fieldAddressIsoOnTcp.focus();
				}).always(function() {
					iconLookup.removeClass('fa-spinner fa-spin fa-fw').addClass('fa-search');
					lookupPlcPNBtn.removeClass('disabled');
				});
			});

			flashPlcPNBtn.click(function() {
				if (flashPlcPNBtn.hasClass('disabled')) return;

				if (!lookupPlcPNValue) {
					flashPlcPNBtn.addClass('disabled');
					return;
				}

				var macaddr = lookupPlcPNValue['MAC Address'].replace(/:/g, '-');
				var tgtUrl = '__node-red-contrib-s7/flashled/iso-on-tcp/' + macaddr;
				$.getJSON(tgtUrl);
			});

		},
		oneditsave: function () {
			var node = this;
			var vars = $("#node-config-input-variables-container").children();
			node.vartable = [];

			vars.each(function (i) {
				var elm = $(this);
				var addr = elm.find(".node-config-input-variable-addr").val();
				var name = elm.find(".node-config-input-variable-name").val();
				var v = {
					addr: addr,
					name: name || addr
				}
				node.vartable.push(v);
			});
		}
	});
</script>

<!-- ######################################################################################## -->

<script type="text/x-red" data-template-name="s7 in">
	<div class="form-row">
		<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.in.label.endpoint"></span></label>
		<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.in.label.endpoint">
	</div>
	<div class="form-row">
		<label for="node-input-mode"><i class="fa fa-sliders"></i> <span data-i18n="s7.in.label.mode"></span></label>
		<select type="text" id="node-input-mode">
			<option value="single" data-i18n="s7.in.mode.single"></option>
			<option value="all-split" data-i18n="s7.in.mode.all-split"></option>
			<option value="all" data-i18n="s7.in.mode.all"></option>
		</select>
	</div>
	<div class="form-row s7-input-var-row">
		<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="s7.in.label.variable"></span></label>
		<select type="text" id="node-input-variable">
		</select>
    <span id="s7-custom-var-addr"></span>
	</div>
	<div class="form-row">
		<label>&nbsp;</label>
		<input type="checkbox" id="node-input-diff" style="display: inline-block; width: auto; vertical-align: top;">
		<label for="node-input-diff" style="width:70%;"><span data-i18n="s7.in.label.diff"></span></label>
	</div>
	<div class="form-row">
		<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
		<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
	</div>
</script>

<script type="text/x-red" data-help-name="s7 in">
	<p>Reads data from a S7 PLC</p>
	<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>

	<h3>Outputs</h3>
	<dl class="message-properties">
		<dt>payload<span class="property-type">any</span></dt>
		<dd>
			The value(s) as read from the PLC. The format and type of the payload
			depends on the configured "Mode"
		</dd>

		<dt>topic<span class="property-type">string</span></dt>
		<dd>
			The name of the variable, when the message refers to a single variable
			(that is, when mode is "Single Variable" or "All variables, one per
			message")
		</dd>
	</dl>
	
	<h3>Details</h3>
	<p>
		All data is read cyclically from the PLC as configured in the <i>s7 endpoint</i>, 
		but there are three modes of making it available in a flow:
	</p>
	<ul>
		<li>
			<b>Single variable:</b> A single variable can be selected from the configured 
			variables, and a message is sent every cycle, or only when it changes if 
			<i>diff</i> is checked. <code>msg.payload</code> contains the variable's value
			and <code>msg.topic</code> has the variable's name.
		</li>
		<li>
			<b>All variables, one per message:</b> Like the <i>Single variable</i> mode, 
			but for all variables configured. If <i>diff</i> is checked, a message is sent
			everytime any variable changes. If <i>diff</i> is unchecked, one message is sent 
			for every variable, in every cycle. Care must be taken about the number of 
			messages per second in this mode.
		</li>
		<li>
			<b>All variables:</b> In this mode, <code>msg.payload</code> contains an object 
			with all configured variables and their values. If <i>diff</i> is checked, a 
			message is sent if at least one of the variables changes its value.
		</li>
	</ul>
</script>

<script type="text/javascript">
	RED.nodes.registerType('s7 in', {
		category: 'plc',
		defaults: {
			endpoint: {
				value: "",
				type: "s7 endpoint",
				required: true
			},
			mode: {
				value: "single"
			},
			variable: {
				value: ""
			},
			diff: {
				value: true
			},
			name: {
				value: ""
			}
		},
		color: "#3FADB5",
		inputs: 0,
		outputs: 1,
		icon: "serial.png",
		label: function () {
			var self = this;

			if (this.name) return this.name;

			var endpointNode = RED.nodes.node(this.endpoint);
			if (endpointNode) {
				return (this.mode == 'single' ? this.variable : null) || endpointNode.label();
			}
			return this._("s7.in.label.name");
		},
		labelStyle: function () {
			return this.name ? "node_label_italic" : "";
		},
		oneditprepare: function () {
			var self = this;

			var varList = $('#node-input-variable');
			var varAddr = $('#s7-custom-var-addr');
			var modeList = $('#node-input-mode');
			var endpointList = $("#node-input-endpoint");
			var vars = [];

			function updateVarList(endpointId) {
				$('#node-input-variable option').remove();

				var endpointNode = RED.nodes.node(endpointId);
				if (!endpointNode) return;
				vars = endpointNode.vartable || [];
				if (typeof vars === 'string') vars = JSON.parse(vars);

				varList.append($('<option/>', {
					disabled: "disabled",
					selected: "selected",
					style: "display:none;",
					text: vars.length ? self._("s7.in.label.variable-select") : self._("s7.in.label.variable-novar")
				}));

				$.each(vars, function (i, val) {
					varList.append($('<option/>', {
						value: val.name || val.addr,
						text: val.name || val.addr
					}));
					if (val.name == self.variable) {
						varList.val(self.variable);
					}
				});
			}

			varList.change(function () {
				$.each(vars, function (i, val) {
					if (varList.val() == val.name) {
						varAddr[0].innerText = val.addr;
						return true;
					}
				});
			});

			endpointList.change(function () {
				updateVarList(endpointList.val());
			});
			updateVarList(self.endpoint);

			modeList.change(function () {
				if (modeList.val() == "single") {
					varList.parent().show();
				} else {
					varList.parent().hide();
				}
			});
			modeList.change();
		}
	});
</script>

<!-- ######################################################################################## -->

<script type="text/x-red" data-template-name="s7 out">
	<div class="form-row">
		<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.out.label.endpoint"></span></label>
		<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.out.label.endpoint">
	</div>
	<div class="form-row s7-input-var-row">
		<label for="node-input-variable"><i class="fa fa-random"></i> <span data-i18n="s7.out.label.variable"></span></label>
		<select type="text" id="node-input-variable">
		</select>
    <span id="s7-custom-var-addr"></span>
	</div>
	<div class="form-row">
		<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
		<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
	</div>
	<div class="form-tips"><span data-i18n="[html]s7.out.disclaimer"></span></div>
</script>

<script type="text/x-red" data-help-name="s7 out">
	<p>Writes to a memory area in a S7 PLC</p>
	<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>

	<p class="form-tips">
		<b>Caution when writing data to production PLCs!</b><br/>
		Fully test everything you do. The authors cannot be liable for any
		damage or injury caused by the use of this node
	</p>

	<h3>Inputs</h3>
	<dl class="message-properties">
		<dt>payload<span class="property-type">string | number | boolean | array</span></dt>
		<dd>
			the data to be written. It will be casted to the required data type
			whenever possible.
		</dd>
		
		<dt>variable<span class="property-type">string | array</span></dt>
		<dd>
			the variable to write to on the PLC, according to the variable list
			on the PLC endpoint. This takes effect <i>only</i> if the "Variable"
			field is left empty on the configuration.
		</dd>
	</dl>

	<h3>Details</h3>
	<p>
		Writes data to the PLC on the variable specified on the node configuration
		or on <code>msg.variable</code>.
	</p>
	<p>
		It is possible to write to multiple variables at once, by setting both 
		<code>msg.variable</code> and <code>msg.payload</code> to arrays containing 
		the variable names and their values, respectively. Note that they may not be
		written in the same write cycle to the PLC, depending on the amount of variables 
		to be written.
	</p>
</script>

<script type="text/javascript">
	RED.nodes.registerType('s7 out', {
		category: 'plc',
		defaults: {
			endpoint: {
				value: "",
				type: "s7 endpoint",
				required: true
			},
			variable: {
				value: ""
			},
			name: {
				value: ""
			}
		},
		color: "#3FADB5",
		inputs: 1,
		outputs: 0,
		icon: "serial.png",
		align: 'right',
		label: function () {
			var self = this;

			if (this.name) return this.name;

			var endpointNode = RED.nodes.node(this.endpoint);
			if (endpointNode) {
				return this.variable || endpointNode.label();
			}
			return this._("s7.out.label.name");
		},
		labelStyle: function () {
			return this.name ? "node_label_italic" : "";
		},
		oneditprepare: function () {
			var self = this;

			var varList = $('#node-input-variable');
			var varAddr = $('#s7-custom-var-addr')[0];
			var endpointList = $("#node-input-endpoint");
			var vars = [];

			function updateVarList(endpointId) {
				$('#node-input-variable option').remove();

				varList.append($('<option/>', {
					//disabled: "disabled",
					selected: "selected",
					//style: "display:none;",
					text: self._("s7.out.label.variable-select"),
					value: ""
				}));

				var endpointNode = RED.nodes.node(endpointId);
				if (!endpointNode) return;
				vars = endpointNode.vartable || [];
				if (typeof vars === 'string') vars = JSON.parse(vars);

				$.each(vars, function (i, val) {
					varList.append($('<option/>', {
						value: val.name || val.addr,
						text: val.name || val.addr
					}));
					if (val.name == self.variable) {
						varList.val(self.variable);
					}
				});
			}

			varList.change(function () {
				varAddr.innerText = ""
				$.each(vars, function (i, val) {
					if (varList.val() == val.name) {
						varAddr.innerText = val.addr;
						return true;
					}
				});
			});

			endpointList.change(function () {
				updateVarList(endpointList.val());
			});
			updateVarList(self.endpoint);
		}
	});
</script>

<!-- ######################################################################################## -->

<script type="text/x-red" data-template-name="s7 control">
	<div class="form-row">
		<label for="node-input-endpoint"><i class="fa fa-bolt"></i> <span data-i18n="s7.control.label.endpoint"></span></label>
		<input type="text" id="node-input-endpoint" data-i18n="[placeholder]s7.control.label.endpoint">
	</div>
	<div class="form-row">
		<label for="node-input-function"><i class="fa fa-sliders"></i> <span data-i18n="s7.control.label.function"></span></label>
		<select type="text" id="node-input-function">
			<option value=""></option>
			<option value="cycletime" data-i18n="s7.control.function.cycletime"></option>
			<option value="trigger" data-i18n="s7.control.function.trigger"></option>
		</select>
	</div>
	<div class="form-row">
		<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="s7.label.name"></span></label>
		<input type="text" id="node-input-name" data-i18n="[placeholder]s7.label.name">
	</div>
</script>

<script type="text/x-red" data-help-name="s7 control">
	<p>Enables advanced control of the PLC and the connection</p>
	<p>This node was created as part of the <a href="https://st-one.io" target="_blank">ST-One</a> project</p>
	
	<h3>Details</h3>
	<p>The behavior of this node is changed according to the selected function. Each function
		has its own configuration, expects different parameters in the messages, and sends
		different messages out
	</p>
	<dl class="message-properties">
		<dt>Cycle Time</dt>
		<dd>
			Changes the time interval between each cyclic read 
			of variables. It expects a message with <code>payload</code> with a 
			positive number, being the time in milliseconds between each read. A 
			value of zero disables the cyclic read.
		</dd>

		<dt>Trigger read</dt>
		<dd>
			Manually triggers a read cycle. No message parameters are used and the 
			same message is sent on the output. Useful when longer cycle times are 
			used, but an instant feedback is needed (for example after changing a 
			variable). Note that the <i>s7 in</i> nodes are still required to read
			the values of the variables.
		</dd>
	</dl>
</script>

<script type="text/javascript">
	RED.nodes.registerType('s7 control', {
		category: 'plc',
		defaults: {
			endpoint: {
				value: "",
				type: "s7 endpoint",
				required: true
			},
			function: {
				value: "cycletime"
			},
			name: {
				value: ""
			}
		},
		color: "#3FADB5",
		inputs: 1,
		outputs: 1,
		icon: "serial.png",
		label: function () {
			var self = this;

			if (this.name) return this.name;

			var endpointNode = RED.nodes.node(this.endpoint);
			if (endpointNode) {
				if (this.function) {
					return endpointNode.label() + ": " + this._("s7.control.function." + this.function);
				} else {
					return endpointNode.label()
				}
			}
			return this._("s7.control.label.name");
		},
		labelStyle: function () {
			return this.name ? "node_label_italic" : "";
		}
	});
</script>
